package com.thtfpc.ssdp;

import com.thtfpc.utility.ThreadUtil;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.DatagramSocket;
import java.util.UUID;

/**
 * 供SSDP协议实现的UDP单点和组播通讯的基础通讯类 *
 * <pre>
 * SSDPChannel是抽象类， 子类需实现onMessage方法，以实现当SSDPMessage到达后，子类的行为逻辑 *
 * onStart、onStopping、onStop 方法分别定义了启动后、关闭前、关闭后等事件，子类可根据情况进行截断添加逻辑。
 * </pre>
 */
public abstract class SSDPChannel implements Runnable {
    public static String TAG = SSDPChannel.class.getSimpleName();
    public static String MULTICAST_ADDRESS = "239.255.255.250";
    public static int MULTICAST_PORT = 1900;

    protected DatagramSocket multicastSocket;
    protected InetAddress broadcastAddr;
    protected int port;
    private volatile boolean running;
    protected byte[] receiveBuffer;
    protected DatagramPacket receivePacket;
    protected Thread receiveThread;
    protected String deviceId;
    protected boolean multicast;

    /**
     * 该构造模式下， 默认使用组播协议
     *
     * @param bindPort 监听端口
     */
    public SSDPChannel(int bindPort) {
        this(bindPort, true);
    }

    /**
     * @param bindPort  监听端口
     * @param multicast 是否加入组播
     */
    public SSDPChannel(int bindPort, boolean multicast) {
        this.port = bindPort;
        this.deviceId = UUID.randomUUID().toString();
        this.receiveBuffer = new byte[1024 * 4];
        this.receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
        this.multicast = multicast;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public boolean isRunning() {
        return running;
    }


    /**
     * 启动SSDP网络监听
     * <p>
     * 在实例调用stop后，可重新调用start
     *
     * @throws IOException
     */
    public void start() throws IOException {
        synchronized (this) {
            if (running == false) {
                multicastSocket = (this.multicast) ? new MulticastSocket(this.port) : new DatagramSocket(this.port);
                if (multicast) {
                    broadcastAddr = InetAddress.getByName(MULTICAST_ADDRESS);
                    ((MulticastSocket) multicastSocket).joinGroup(broadcastAddr);
                }
                receiveThread = new Thread(this, "receive_thread");
                receiveThread.start();
                while(running == false) {
                    try {
                        wait();
                    }
                    catch (InterruptedException ex){
                    }
                }
            }
        }
    }


    /**
     * 关闭SSDP网络监听
     *
     * @throws IOException
     */
    public void stop() throws IOException {
        synchronized (this) {
            if (running == true) {
                onStopping();
                running = false;
                if (multicast) {
                    ((MulticastSocket) multicastSocket).leaveGroup(broadcastAddr);
                }
                multicastSocket.close();
                if (receiveThread != null) {
                    try {
                        ThreadUtil.join(receiveThread);
                    }
                    catch (Exception ex) {
                    }
                    receiveThread = null;
                }
                onStop();
            }
        }
    }


    @Override
    public void run() {
        synchronized (this) {
            notifyAll();
            running = true;
            onStart();
        }
        try {
            do {
                multicastSocket.receive(receivePacket);
                onDatagramPacket(receivePacket);
            }
            while (running);
        }
        catch (IOException ex) {
            System.out.println(ex.toString());
        }
        //System.out.println(TAG + ".receiveThread.run() quit");
    }


    protected void onDatagramPacket(DatagramPacket datagramPacket) {
        SSDPMessage message = SSDPMessage.parseFrom(datagramPacket.getData(), datagramPacket.getOffset(), datagramPacket.getLength());
        message.setAddress(datagramPacket.getAddress().getHostAddress());
        message.setPort(datagramPacket.getPort());
        if (message != null) {
            onMessage(message);
        }
    }


    public void sendPacket(DatagramPacket packetSend) {
        try {
            this.multicastSocket.send(packetSend);
        }
        catch (IOException ex) {
            System.out.println(ex.toString());
        }
    }


    /**
     * 发送SSDPMessage代表的SSDP消息提到组播网络中
     *
     * @param ssdpMessage SSDP组播消息；
     *                    ssdpMessage.address:ssdpMessage.port是组播或者点播消息的目标地址
     * @see SSDPMessage
     */
    public void sendMessage(SSDPMessage ssdpMessage) {
        byte[] dataBuffer = ssdpMessage.toByteArray();
        sendPacket(new DatagramPacket(dataBuffer, 0, dataBuffer.length, new InetSocketAddress(ssdpMessage.getAddress(), ssdpMessage.getPort())));
    }


    protected abstract void onMessage(SSDPMessage ssdpMessage);


    protected void onStart() {
    }


    protected void onStopping() {
    }


    protected void onStop() {
    }
}
